2020-0722-Binary Security of WebAssembly
会议:USENIX SECURITY’20
论文名称:Everything Old is New Again: Binary Security of WebAssembly
链接:https://www.usenix.org/system/files/sec20-lehmann.pdf
Introduction
WebAssembly是由W3C社区组开发的一项新技术。WebAssembly允许开发人员将他们本机的C/C++代码带到浏览器,代码由最终用户以接近本机的性能运行。WebAssembly已经在所有主流浏览器的最新版本中得到广泛支持,目前正在许多基于Web的服务中被使用。
与native执行的程序相比,WebAssembly binary少了很多的防御措施,如Virtual memory, Stack canaries, Control-Flow Integrity (CFI)。WebAssembly社区也认为这些缓解措施不是必要的。
“Data execution prevention and stack smashing protection are not needed by WebAssembly programs.”
github.com/WebAssembly/design
“At worst, a buggy or exploited WebAssembly program can make a mess of the data in its own memory.”
Haas et al., PLDI 2017
作者针对WebAssembly的进行了深入研究,作者的主要贡献:
- 对WebAssembly的线性内存、缓解机制进行了深度的分析,研究了由C、C++、Rust编译成的wasm的安全性比native binary低的原因。
- 提供了一组攻击原语
- 提供了三个应用中的攻击案例
- 证明data、control-flow 攻击在WebAssembly中是可行的
- 讨论了加固方案
Background on WebAssembly
Overview
- wasm的可读形式称为wat
- wasm一个module对应一个文件,包含functions、globals,以及一个linear memory和indirect call table
- wasm bytecode是在一个基于栈的虚拟机上执行
- wasm中的间接跳转、间接调用使用全局table中的index来表示,常量字符串使用其偏移来表示。
- 目前主流的WebAssembly编译器Emscripten使用LLVM作为前端,fastcomp作为后端。在LLVM IR层面还未进行修改。
相关介绍:
Type
WebAssembly中的globals、locals、arguments、函数返回值、instructions都规定了类型,binary在执行前会进行静态的类型检查。
WebAssembly中主要的四种基本类型:i32, i64, f32, f64。
WebAssembly中没有数组、records、designed pointers等复杂类型,在编译时会将这些source-level类型转为基本类型。
Control-Flow
WebAssembly中的branch只能在当前函数内跳转,多路branch指令也只能跳转到branch table中的目的地址。
memory中的data是不能作为bytecode来执行的。
在x86中的多种利用方式失效(injecting shellcode,使用indirect jump劫持控制流)
Indirect Calls
处理indirect call的方式:pop 出的操作数为called table的index,执行前会检查该函数的类型。
关于间接调用
程序中共用一个跳转表,w2c_T0
Linear, Unmanaged Memory
WebAssembly提供一块linear menory,可以理解为一个全局数组,可以load、store其中已被分配的内存。用32-bit的pointer表示其中的地址,因此i32作为指针类型。
WebAssembly提供了自己的动态内存分配器,可以使用malloc、free,管理linear memory。
线性内存的真实地址及其中的数据对WebAssembly是隐藏的。 换句话说,WebAssembly无法直接访问内存的内容。WebAssembly将在线性内存的索引处请求数据,浏览器负责确定实际地址。这是WebAssembly比C/C++等本地代码更安全的部分原因。
Host Environment
WebAssembly通过WebAssembly system interface (WASI)与外界交互,如I/O、file、network。
Security Analysis of Linear Memory
Managed vs. Unmanaged Data
Managed Data:local variables, global variables, values on the evaluation stack, and return addresses, reside in dedicated storage handled directly by the VM. WebAssembly 代码不能直接操作这些managed data,只能implicit使用。如local.get 0代表获取local 0,但并没有指针指向local 0。
Unmanaged Data:在linear memory中的所有data,可以由程序操作的。all non-scalar data,字符串,数组,列表,都在linear memory中,因为managed data都是没有地址的。
Memory Layout
The memory layout, i.e., the order of stack, heap, and data in linear memory, depends on the compiler.
Memory Protections
在native program中,最基础的内存保护机制为virtual memory with unmapped pages.
然而WebAssembly中的linear memory,都是被固定分配的,没有ASLR。
linear memory中的数据不能被作为代码执行,但是WebAssembly不允许标记内存为只读,因此linear memory中的所有数据都是可写的。
因此,WebAssembly中的常量并不是read only的,Heap、Stack、Data的位置都是固定的,且可能在两个段之间发生越界。
Attack Primitives
作者从三个方面进行介绍:
- obtaining a write primitive
- the data that can be overwritten
- triggering security-compromising behavior by overwriting data
Obtaining a Write Primitive
- Stack-based Buffer Overflow,覆盖栈上的其他变量
- Stack Overflow,无stack overflow check的情况下,跨frame覆盖变量
- Heap Metadata Corruption,Emscripten编译器可以让开发者自行选择分配器,默认的dlmalloc、可以减小程序体积的emmalloc、Rust可选择使用wee_alloc。
- 作者发现emmalloc和wee_alloc的元数据都可能被corruption,emmalloc是一个first-fit的分配器
我自己尝试编译了wasm的程序,发现stack overflow checks在2019年七八月份已经被引入,且默认开启,关闭需手动将ASSERTIONS=0
1 | emcc ./t.c -s ASSERTIONS=0 -s WASM=1 -o hello.html |
Overwriting Data
- Overwriting Stack Data
- Overwriting Heap Data
- Overwriting “Constant” Data
Triggering Unexpected Behavior
Redirecting Indirect Calls
攻击者可以修改indirect call的index来劫持控制流,但是WebAssembly提供了两套缓解机制:不是所有函数都在indirect call table中被声明;all calls(direct and indirect call)都会进行类型检查。
Code Injection into Host Environment
攻击者可以通过覆盖穿进eval的参数,来达到code injection的目的。
Application-specific Data Overwrite
有的WebAssembly程序被用于解释执行CIL/.NET语言,其中就会出现很多影响程序行为的机会。
End-to-End Attacks
作者举了三个攻击的例子。
Cross-Site Scripting in Browsers
Stack-based buffer overflow (CVE-2018-14550)
攻击者覆盖img标签成script标签,以达到XSS的效果。
Remote Code Execution in Node.js
- 无CVE编号(可能自己编的例子)
- emmalloc allocator
通过Heap metadata corruption,unsafe unlink时获得一次任意地址写的机会,修改Indirect call的function index来RCE。
Arbitrary File Write in Stand-alone VM
无CVE编号
通过Stack-based buffer overflow来覆盖String(在stack紧临着data的情况下),达到任意文件写
Quantitative Evaluation
实验对象:9 binaries from real-world, 17 C and C++ programs from SPEC CPU 2017 benchmark suite
Measuring Unmanaged Stack Usage
因为获得write primitive是攻击的入口,有多少数据被存储在unmanaged stack上呢?
由于WebAssembly中用全局变量来标识栈帧,没有x86中的rsp。作者使用启发式的方法来识别栈帧变量:在整个程序中被使用次数最多的即为栈帧变量。
WebAssembly function的prologues:
Measuring Indirect Calls and Targets
作者统计了测试集中的indirect call和indirectly callable function的数量、占比:(平均值为9.8%,49.2%)
Comparing with Existing CFI Policies
作者将WebAssembly中的间接调用类型检查与CFI进行了对比(上图、下图中数据所示)
用了两个指标来表示安全性:
- The class count, i.e., how many different classes exist
- The sizes of the classes, i.e., how many targets are in each class
Trying WebAssembly
Memory Layout
针对Figure 4中的a b c三个环境进行研究。
source
1 |
|
emcc
版本:
1 | parallels@Ubuntu:~/webassembly$ emcc -v |
1 | heap1: 0x500ef8 |
clang
1 | clang --target=wasm32-unknown-wasi --sysroot /home/parallels/webassembly/wasi-sdk-11.0/share/wasi-sysroot/ -O0 -g -o example.wasm t.c |
1 | parallels@Ubuntu:~/webassembly$ wasmtime ./example.wasm |
clang with stack-first
1 | clang --target=wasm32-unknown-wasi --sysroot /home/parallels/webassembly/wasi-sdk-11.0/share/wasi-sysroot/ -O0 -Wl,--stack-first -g -o example_stack_first.wasm t.c |
1 | parallels@Ubuntu:~/webassembly$ wasmtime ./example_stack_first.wasm |
Found something
- 图a emcc中的两个差异
- clang生成的wasm,其中的unused不超过0x10000,可能可以越界?
- Heap都可能会被buffer overflow corrupt
Stack checks
- 在emcc中已默认开启stack checks
- clang中未默认开启
无符号wasm的逆向
wasm module类似静态链接的binary,各种库函数都杂在里面,无符号时可能会为逆向带来一定的难度。
Function
- w2c_memory即为论文中提到的linear memory
- store(memory, a2, a3)函数等价于 *(memory+a2) = a3
- ret = load(memory, a2)函数等价于 ret = *(memory+a2)
- scanf、printf的第一个参数就是data_segment中的offset
- linear memory搜交叉引用可以发现其在init_memory函数中被初始化,使用memcpy将数个数组拷贝进去,所以说反编译出来的那几个数组即为linear memory,offset可以很容易的找到
Import functions
倒入函数的识别
可以通过从init函数中的init_table函数,或者是通过确认是printf等导入函数内的跟踪,找到一个全局变量T0,其为导入函数的table,所有导入的函数在其中被赋值,调用时也会通过该表进行间接函数调用。
因此,我们可以通过init_table函数中进行识别,恢复出部分与导入函数相关的function names
关于dlmalloc
WebAssembly默认使用dlmalloc,dlmalloc属于inplace metadata堆管理器。
所以,可能可以覆盖Heap中的metadata?
Related works
- CCS’19 Poster: Detecting WebAssembly-based Cryptocurrency Mining
- 通过trace 挖矿程序和游戏之间,出现频率最高的指令序列,进行建模,以分辨挖矿程序和正常游戏。跟WebAssembly没啥关系…
- S&P’19 Formally Verified Cryptographic Web Applications in WebAssembly
- 形式化验证,看不太懂…
- BlackHat USA’19 WebAssembly: A New World of Native Exploits on the Browser
- 粗略看了一下BlackHat USA’19的文章,与本文对比似乎并没有什么创新点(作者无交集),本文只是较为系统的介绍了WebAssembly的部分机制及攻击方式。